package model

import (
	"errors"
	"fmt"
	"github.com/jinzhu/gorm"
	"github.com/sirupsen/logrus"
	"gopkg.in/yaml.v2"
	"io/ioutil"
)

var ErrDuplicatedMenu = errors.New("菜单重复")

type (
	MenuTrees []*MenuTree

	MenuTree struct {
		ID        uint        `json:"id" yaml:"id"`
		Title     string      `json:"title" yaml:"title"`
		Icon      string      `json:"icon" yaml:"icon"`
		Path      string      `json:"path" yaml:"path"`
		Component string      `json:"component" yaml:"component"`
		Sequence  int         `json:"sequence" yaml:"sequence"`
		ParentID  uint        `json:"parent_id" yaml:"-"`
		Actions   MenuActions `json:"actions" yaml:"actions"`
		Children  MenuTrees   `json:"children" yaml:"children"`
	}

	Menus []*Menu

	Menu struct {
		Model
		Title     string      `json:"title" gorm:"column:title;size:255;uniqueIndex;default:'';not null;"`
		ParentID  uint        `json:"parent_id" gorm:"column:parent_id;index;not null;"`
		Icon      string      `json:"icon" gorm:"column:icon;size:255;"`
		Path      string      `json:"path" gorm:"column:path;size:255;default:'';not null;"`
		Component string      `json:"component" gorm:"column:component;size:255;default:'';not null;"`
		Sequence  int         `json:"sequence" gorm:"column:sequence;default:0;not null;"`
		Actions   MenuActions `json:"actions" gorm:"-"`
	}
)

// InitMenu 用于通过菜单配置文件初始化菜单,
// 默认会只创建不存在的菜单项, 已经存在于数据库中的将跳过
func InitMenu(dataFile string) {
	if dataFile == "" {
		logrus.Warn("初始化菜单程序跳过执行, 未指定菜单配置文件")
		return
	}

	menus, err := readMenuDataFile(dataFile)
	if err != nil {
		logrus.Warnf("尝试读取并解析菜单配置文件失败, 将跳过初始化菜单程序, 错误原因: %s", err)
		return
	}

	tx := db.Begin()

	if err := createMenus(0, menus, tx); err != nil {
		tx.Rollback()
		logrus.Warnf("初始化菜单程序执行失败, 错误原因: %s", err)
		return
	}

	logrus.Info("初始化菜单程序执行成功, 菜单已加载至应用数据库!")
	tx.Commit()
	return
}

func createMenus(parentID uint, list MenuTrees, tx *gorm.DB) error {
	for _, item := range list {
		menu := &Menu{
			Title:     item.Title,
			ParentID:  parentID,
			Icon:      item.Icon,
			Path:      item.Path,
			Component: item.Component,
			Sequence:  item.Sequence,
			Actions:   item.Actions,
		}

		newMenu, err := createMenu(menu, tx)
		if err != nil && !errors.Is(err, ErrDuplicatedMenu) {
			return err
		} else if err != nil && errors.Is(err, ErrDuplicatedMenu) {
			continue
		}

		if item.Children != nil && len(item.Children) > 0 {
			if err := createMenus(newMenu.ID, item.Children, tx); err != nil {
				return err
			}
		}
	}
	return nil
}

func readMenuDataFile(dataFile string) (MenuTrees, error) {
	bytesBuf, err := ioutil.ReadFile(dataFile)
	if err != nil {
		return nil, err
	}

	data := make(MenuTrees, 0)
	if err := yaml.Unmarshal(bytesBuf, &data); err != nil {
		return nil, err
	}

	return data, nil
}

func createMenu(menu *Menu, tx *gorm.DB) (*Menu, error) {
	// 查询重复菜单，重复菜单条件: 同处于同一个Parent下且名称重复
	duplicated := make([]Menu, 0)
	if err := db.Where("parent_id = ? and title = ?", menu.ParentID, menu.Title).Find(&duplicated).Error; err != nil {
		return nil, fmt.Errorf("添加菜单出现系统错误, %w", err)
	}

	if len(duplicated) != 0 {
		return nil, ErrDuplicatedMenu
	}

	if err := tx.Create(menu).Error; err != nil {
		return nil, fmt.Errorf("添加菜单失败, %w", err)
	}

	if err := CreateActions(int(menu.ID), menu.Actions, tx); err != nil {
		return nil, fmt.Errorf("添加菜单动作失败, %w", err)
	}

	return menu, nil
}

func GetAllMenus(tx *gorm.DB) (Menus, error) {
	if tx == nil {
		tx = db
	}
	menus := make([]*Menu, 0)

	if err := tx.Model(&Menu{}).Find(&menus).Error; err != nil {
		return nil, err
	}

	for _, menu := range menus {
		actions, err := GetMenuActionsWithMenuID(int(menu.ID))
		if err != nil {
			return nil, err
		}

		menu.Actions = actions
	}

	return menus, nil
}

func AddMenu(menu *Menu) error {
	// 查询重复菜单，重复菜单条件: 同处于同一个Parent下且名称重复
	duplicated := make([]Menu, 0)
	if err := db.Where("parent_id = ? and title = ?", menu.ParentID, menu.Title).Find(&duplicated).Error; err != nil {
		return fmt.Errorf("添加菜单出现系统错误, %w", err)
	}

	if len(duplicated) != 0 {
		return fmt.Errorf("待添加的菜单名称[%s]重复", menu.Title)
	}

	tx := db.Begin()

	if err := tx.Create(menu).Error; err != nil {
		tx.Rollback()
		return fmt.Errorf("添加菜单失败, %w", err)
	}

	if err := CreateActions(int(menu.ID), menu.Actions, tx); err != nil {
		tx.Rollback()
		return fmt.Errorf("添加菜单动作失败, %w", err)
	}

	tx.Commit()
	return nil
}

func GetMenus(index, size int) ([]*Menu, int, error) {
	menus := make([]*Menu, 0)
	total := 0
	if err := db.Model(&Menu{}).Count(&total).Error; err != nil {
		return nil, -1, err
	}

	if err := db.Model(&Menu{}).Offset((index - 1) * size).Limit(size).Find(&menus).Error; err != nil {
		return nil, -1, err
	}

	return menus, total, nil
}

func GetMenuWithIDs(ids []uint) (Menus, error) {
	menus := make(Menus, 0)
	if err := db.Model(&Menu{}).Find(&menus, ids).Error; err != nil {
		return nil, err
	}

	for _, menu := range menus {
		actions, err := GetMenuActionsWithMenuID(int(menu.ID))
		if err != nil {
			return nil, err
		}
		menu.Actions = actions
	}

	return menus, nil
}

func GetMenuWithID(id int) (*Menu, error) {
	menu := new(Menu)

	if err := db.Model(menu).Where("id = ?", id).Find(menu).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fmt.Errorf("无法找到ID为%d的菜单", id)
		} else {
			return nil, err
		}
	}

	actions, err := GetMenuActionsWithMenuID(id)
	if err != nil {
		return nil, err
	}

	menu.Actions = actions

	return menu, nil
}

func DeleteMenu(id int) error {
	menu, err := GetMenuWithID(id)
	if err != nil {
		return err
	}

	tx := db.Begin()

	// 删除ActionResources
	if err := DeleteActionResourcesByMenuID(id, tx); err != nil {
		tx.Rollback()
		return err
	}
	// 删除Actions
	if err := DeleteActionsByMenuID(id, tx); err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Delete(menu).Error; err != nil {
		tx.Rollback()
		return err
	}

	tx.Commit()
	return nil
}

func UpdateMenu(id int, change *Menu) error {
	if id == int(change.ParentID) {
		return errors.New("无效的父级节点")
	}

	menu, err := GetMenuWithID(id)
	if err != nil {
		return err
	}

	tx := db.Begin()

	if err := updateMenuActions(id, menu.Actions, change.Actions, tx); err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Model(menu).Updates(change).Error; err != nil {
		tx.Rollback()
		return err
	}

	tx.Commit()
	return nil
}

func (m Menus) ToTree() MenuTrees {
	menuTreeList := make(MenuTrees, len(m))
	for idx, menu := range m {
		// 先把所有的Menu统一转换为MenuTree
		menuTreeList[idx] = &MenuTree{
			ID:        menu.ID,
			Title:     menu.Title,
			Icon:      menu.Icon,
			Path:      menu.Path,
			Component: menu.Component,
			Sequence:  menu.Sequence,
			ParentID:  menu.ParentID,
			Actions:   menu.Actions,
		}
	}

	// 转换为树形结构
	menuTreeMap := make(map[uint]*MenuTree)
	for _, treeItem := range menuTreeList {
		menuTreeMap[treeItem.ID] = treeItem
	}

	trees := make(MenuTrees, 0)

	for _, treeItem := range menuTreeList {
		// ParentID 为0则为父级菜单
		if treeItem.ParentID == 0 {
			trees = append(trees, treeItem)
			continue
		}

		if parentMenuTree, ok := menuTreeMap[treeItem.ParentID]; ok {
			// 判断此菜单项的父级菜单是否拥有Children, 如果为nil则使用当前MenuTree进行初始化
			if parentMenuTree.Children == nil {
				parentMenuTree.Children = MenuTrees{treeItem}
				continue
			} else {
				parentMenuTree.Children = append(parentMenuTree.Children, treeItem)
			}
		}
	}

	return trees
}

func updateMenuActions(menuID int, oldActions, newActions MenuActions, tx *gorm.DB) error {
	addActions, delActions, updateActions := compareActions(oldActions, newActions)

	if err := CreateActions(menuID, addActions, tx); err != nil {
		return err
	}

	for _, action := range delActions {
		if err := DeleteAction(action.ID, tx); err != nil {
			return err
		}
		if err := DeleteActionResourcesByActionID(action.ID, tx); err != nil {
			return err
		}
	}

	oldActionMap := oldActions.ToMap()
	for _, action := range updateActions {
		if err := UpdateAction(int(action.ID), action, tx); err != nil {
			return err
		}
		oldAction := oldActionMap[action.Slug]

		addList, delList := compareActionResources(oldAction.Resources, action.Resources)

		for _, resource := range addList {
			resource.ActionID = oldAction.ID
			if err := CreateActionResource(resource, tx); err != nil {
				return err
			}
		}

		for _, resource := range delList {
			if err := DeleteActionResource(resource.ID, tx); err != nil {
				return err
			}
		}
	}

	return nil
}

func compareActionResources(old, new ActionResources) (addList, delList ActionResources) {
	oldResourcesMap := old.ToMap()
	newResourcesMap := new.ToMap()

	for k, resource := range newResourcesMap {
		if _, ok := oldResourcesMap[k]; ok {
			delete(oldResourcesMap, k)
			continue
		}
		addList = append(addList, resource)
	}

	for _, resource := range oldResourcesMap {
		delList = append(delList, resource)
	}

	return
}

func compareActions(oldActions, newActions MenuActions) (addActions, delActions, updateActions MenuActions) {
	newActionsMap := newActions.ToMap()
	oldActionsMap := oldActions.ToMap()

	for k, action := range newActionsMap {
		if _, ok := oldActionsMap[k]; ok {
			updateActions = append(updateActions, action)
			delete(oldActionsMap, k)
			continue
		}
		addActions = append(addActions, action)
	}

	for _, action := range oldActionsMap {
		delActions = append(delActions, action)
	}
	return
}
